#pragma once

#include "node.h"
#include "sfc/fs/path.h"

namespace sfc::serde::json {

using fmt::DebugTuple;
using fmt::DebugList;
using fmt::DebugSet;
using fmt::DebugMap;

template <class Fmter>
class DebugStruct {
  Fmter& _fmt;
  usize _cnt = 0;

 public:
  explicit DebugStruct(Fmter& fmt) : _fmt{fmt} {
    _fmt.write_str("{");
  }

  ~DebugStruct() {
    _fmt.write_str("}");
  }

  DebugStruct(const DebugStruct&) = delete;

  void field(Str name, const auto& value) {
    if (_cnt != 0) {
      _fmt.write_str(", ");
    }

    _fmt.write_str("\"");
    _fmt.write_str(name);
    _fmt.write_str("\": ");
    _fmt.write(value);

    _cnt += 1;
  }

  void fields(auto iter) {
    iter.for_each([&](auto&& item) {
      const auto& [k, v] = item;
      this->entry(k, v);
    });
  }
};

template <class W>
class Formatter {
  fmt::Formatter<W> _inn;

 public:
  Formatter(W& out) : _inn{out} {}

  auto style() const -> const fmt::Style& {
    return _inn.style();
  }

  void write_str(Str val) {
    _inn.write_str(val);
  }

  void pad(Str val) {
    _inn.write_chr('"');
    _inn.write_str(val);
    _inn.write_chr('"');
  }

  void pad_num(bool is_neg, str::Str body) {
    if (is_neg) {
      _inn.write_str(" ");
    }
    _inn.write_str(body);
  }

  void write(const auto& val) {
    if constexpr (requires { val.fmt(*this); }) {
      val.fmt(*this);
    } else {
      fmt::Display{val}.fmt(*this);
    }
  }

  auto debug_tuple() -> DebugTuple<Formatter> {
    return DebugTuple{*this};
  }

  auto debug_list() -> DebugList<Formatter> {
    return DebugList{*this};
  }

  auto debug_set() -> DebugSet<Formatter> {
    return DebugSet{*this};
  }

  auto debug_map() -> DebugMap<Formatter> {
    return DebugMap{*this};
  }

  auto debug_struct() -> DebugStruct<Formatter> {
    return DebugStruct{*this};
  }
};

void write(auto& out, const auto& val) {
  Formatter{out}.write(val);
}

auto format(const auto& val) -> String {
  auto res = String{};
  Formatter<String>{res}.write(val);
  return res;
}

auto parse(Str json_str) -> Option<Node>;

auto load(fs::Path path) -> Option<Node>;

}  // namespace sfc::serde::json
